Flutterのcontextの「お気持ち」を理解する #Flutter |
您所在的位置:网站首页 › flutter build context › Flutterのcontextの「お気持ち」を理解する #Flutter |
ここでいう「お気持ち」というのは、「定義」とか「正体」とかではなく、 「何のためにあるのか」とか「どう使うことが想定されているのか」とか そういうことを意味します。「作った人がどういうつもりだったか」という意味で「お気持ち」と言っています。 私がFlutterを始めてすぐの頃、contextとかBuildContextなるものが何なのか、どう使うのか、さっぱり理解できなくて困ったので、その辺を助ける記事です。 「実体はなんなのか」とか「正体はなんなのか」といった説明はしませんのでご了承ください。 一番言いたい結論は Hoge.of(context) で「引数に与えた文脈におけるHoge」 を意味するということです。 BuildContext型のcontext変数contextとサラッと言ってますが、より具体的にはBuildContextというクラスですね。 BuildContextクラスのドキュメント こちらをよく読むと実は「お気持ち」も書いてあるのですが、簡潔に終わってますので、この記事で改めて説明します。 "context"は「文脈」という意味そもそも何で"context"と呼ばれているのか、を考えてみることにしましょう。そこには命名者の意図があるはずです。 "context"という英単語は「文脈」という意味です。 「この文脈における『適当』は『雑』ではなく『ちょうどいい』という意味です。」 「この文脈における『押すなよ!』というのは『押せ!』という意味です。」 「この文脈においては、お茶漬けを勧めているのではなく『帰れ』と言っています。」 のように使いますね。 「文脈」は「環境」や「状況」と言い換えることもできます。「この状況における『適当』は『雑』ではなく『ちょうどいい』という意味です。」 「この環境における『押すなよ!』というのは『押せ!』という意味です。」 「この状況においては、お茶漬けを勧めているのではなく『帰れ』と言っています。」 Hoge.of(context)で「この文脈におけるHoge」Flutterの話に戻ります。 Flutterのcontextもやはり「文脈」「状況」「環境」を表します。 「今、どんな場所にいるのか」を表しているわけです。 もっと言うと、「Widgetツリー内のどこにいるか」を表しています。 Flutterのコードでcontextが出てくるのは大体次の2つの場合です。 builder of一つずつ説明します。 builder builder:(BuildContext context){ return // (何かしらのWidget); }builderは、Widget生成してWidgetツリー内にセットする時に呼ばれる関数です。 引数のcontextは、Flutterが自動的に与えます。我々はbuilder関数がcontextを受け取れるようにさえしておけば良いです。 自分でWidgetを作る場合は @override Widget build(BuildContext context){ return // (何かしらのWidget); }となりますが、BuildContextを受け取ってWidgetを返す関数という意味で同じです。 これらのコードに出てくるcontextは、受け取り口として用意されているものであって、使ってるわけじゃないですね。 では次。 of Hoge.of(context).// (何かしらのプロパティやメソッド);こちらは、すでに持っているcontextという変数を実際に使っています。 Hoge.of(context)というのは、contextの場所からWidgetツリーを先祖に向かってさかのぼって、最初に見つかるHogeを返します。 実際にはHogeはThemeだったりDefaultTextStyleだったり自分で作った何かだったりします。 つまりHoge.of(context)は 「引数に与えたcontextはどのHogeの傘下なのか」 「引数に与えたcontextにはどんなHogeが設定されているか」 を調べているわけです。 Themeの例実際のコードで見てみましょう。 次のようなWidgetAを作ります。 class WidgetA extends StatelessWidget { const WidgetA(this.text); final String text; @override Widget build(BuildContext context) { return Center( child: SizedBox( height: 100, width: 200, child: Card( color: Theme.of(context).primaryColor, child: Center(child: Text(text)), ), ), ); } }レイアウト用のWidgetか挟まってて見にくいですが、重要なのはここだけです。 class WidgetA extends StatelessWidget { @override // contextはここで引数として与えられる Widget build(BuildContext context) { return Card( // 自分が置かれた文脈に設定されているThemeを調べている color: Theme.of(context).primaryColor, ), ); } }Theme.of(context)の部分で、自分が置かれた文脈におけるThemeを探しに行き、自分自身の色として適用しています。 このWidgetAを、次のように配置してみましょう。 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData(primaryColor: Colors.pink), home: Scaffold( appBar: AppBar(), body: Column( children: [ Theme( data: ThemeData(primaryColor: Colors.amber), child: const WidgetA('Fuga')), Column( children: [ const WidgetA('Foo'), Theme( data: ThemeData( primaryColor: Colors.teal, ), child: const WidgetA('Bar')) ], ) ], ), )); } }ツリー図にするとこんな感じです。 それぞれのWidgetが何色のThemeの傘下になっているかによって色分けしてあります。 MaterialAppに色が設定してありますが、実はMaterialAppにはデフォルトのThemeを設定する機能がありまして、そこにピンク色を設定してあります。 これを void main() => runApp(MyApp());として実行すると次のようになります。 それぞれのWidgetが、自分の先祖にいるThemeを見つけてその色に染まっているのがわかると思います。 Fooと書いてあるWidgetAは、先祖にThemeがいないので、MaterialAppに設定されているピンク色になっていますね。 また、よく見ると最上段のAppBarもピンク色になっています。(時間や電波状況が表示されている部分) これはScaffoldWidgetの一部なのですが、Scaffoldも、すぐ親にMaterialAppがいるのでピンク色になっています。 【注意】contextの位置が高すぎる場合いちいちWidgetAなんて作らずに、単にズラズラとWidgetを配置していったらどうなるでしょうか? つまりこういうことです。 レイアウト用のコードも挟まってて見にくいので、重要な部分だけ取り出したコードも下に載せておきます。 class MyApp extends StatelessWidget { @override // contextはここで与えられる Widget build(BuildContext context) { return MaterialApp( theme: ThemeData(primaryColor: Colors.pink), home: Scaffold( appBar: AppBar(), body: Column( children: [ Theme( data: ThemeData(primaryColor: Colors.blue), child: Center( child: SizedBox( height: 100, width: 200, child: Card( color: Theme.of(context).primaryColor, child: Center(child: Text('Fuga')), ), ), )), Column( children: [ Center( child: SizedBox( height: 100, width: 200, child: Card( color: Theme.of(context).primaryColor, child: Center(child: Text('Foo')), ), ), ), Theme( data: ThemeData( primaryColor: Colors.green, ), child: Center( child: SizedBox( height: 100, width: 200, child: Card( color: Theme.of(context).primaryColor, child: Center(child: Text('Bar')), ), ), ), ) ], ) ], ), )); } }重要なところだけ版 class MyApp extends StatelessWidget { @override // contextはここで与えられる Widget build(BuildContext context) { return MaterialApp( theme: ThemeData(primaryColor: Colors.pink), home: Column( children: [ Theme( data: ThemeData(primaryColor: Colors.blue), child: Card( color: Theme.of(context).primaryColor, child: Center(child: Text('Fuga')), ), ), Column( children: [ child: Card( color: Theme.of(context).primaryColor, child: Center(child: Text('Foo')), ), Theme( data: ThemeData( primaryColor: Colors.green, ), child: Card( color: Theme.of(context).primaryColor, child: Center(child: Text('Bar')), // 閉じカッコ略 }実行結果がこちら ウィジェットツリー内のThemeとCardの位置関係はまったく変わっていないのに、Cardが自分の先祖にいるThemeにアクセスしてくれません!! 実はコードをよく見ると、引数contextが与えられているのはMyAppのbuild関数だけです。これはMaterialAppよりもさらに親です。 Theme.of(context)は与えられたcontextの地点からスタートしてツリーをさかのぼり、Themeを探しに行くことになりますが そこ(MaterialAppよりも親)からスタートしてさかのぼっても、もうWidgetはありません。もちろんThemeもありません。 ということで、何も見つからないので、3つのCardはFlutterのデフォルトカラーである青色になってしまいました。 Hoge.of(context)でさかのぼりを開始するスタート地点は、そのコードを記述した位置ではなく、引数contextが与えられた位置からなので注意してください。 contextを途中で置き換える方法このようにcontextの与えられる位置が高すぎる場合は、もっと下層でcontextを「発生」させなければなりません。 その方法を2つ紹介します。 自作Widgetを挟むはじめのコードでやっていた方法です。WidgetAのような自作Widgetを用意すれば、自動的にその中にbuild関数があるはずですから、そこでcontextを受け取ることになります。 Builderを使う実はFlutterにはそういう時に使えるWidgetが用意されています。 BuilderというWidgetがそれです。 Builder Class これを挟み込むと、そこでcontextを受け取れます。 参考ということで「お気持ち」の説明は以上です。 より詳しく知りたい方はこちらの記事がおすすめです。 FlutterのBuildContextとは何か また、私の記事ですが、BuildContextと密接な関わりのあるInheritedWidgetについても書きましたのでぜひご覧ください。 InheritedWidgetの目的と使い方【Flutter】 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |